package com.benjaminwan.read.free.model

import android.content.Context
import android.content.res.XmlResourceParser
import android.util.AttributeSet
import android.view.View
import android.widget.LinearLayout
import org.xmlpull.v1.XmlPullParserException
import java.io.IOException
import java.util.*

class Keyboard : LinearLayout, Key.KeyListener {
    private var mShiftCount = 0
    private var mKeyboardListener: KeyboardListener? = null
    private val mKeysWithShiftLabel: MutableList<Key> = mutableListOf()

    interface KeyboardListener {
        fun onKeyDown(keyCode: Int)
        fun onKeyUp(keyCode: Int)
    }

    constructor(context: Context, xmlResourceID: Int) : super(context) {
        setDefaultLayoutConfiguration()
        loadFromXmlResource(context, xmlResourceID)
    }

    constructor(
        context: Context,
        attrs: AttributeSet?,
        xmlResourceID: Int
    ) : super(context, attrs) {
        setDefaultLayoutConfiguration()
        loadFromXmlResource(context, xmlResourceID)
    }

    constructor(
        context: Context,
        attrs: AttributeSet?,
        defStyle: Int,
        xmlResourceID: Int
    ) : super(context, attrs, defStyle) {
        setDefaultLayoutConfiguration()
        loadFromXmlResource(context, xmlResourceID)
    }

    private fun setDefaultLayoutConfiguration() {
        layoutParams = LayoutParams(
            LayoutParams.MATCH_PARENT,
            LayoutParams.MATCH_PARENT
        )
        orientation = VERTICAL
    }

    private fun loadFromXmlResource(
        context: Context,
        xmlResourceID: Int
    ) {
        try {
            val xmlParser = context.resources.getXml(xmlResourceID)
            while (xmlParser.eventType != XmlResourceParser.END_DOCUMENT) {
                if (xmlParser.eventType == XmlResourceParser.START_TAG
                    && xmlParser.name == XML_TAG_LAYOUT
                ) {
                    addView(parseKeyLayout(context, xmlParser))
                }
                xmlParser.next()
            }
            xmlParser.close()
        } catch (e: XmlPullParserException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    @Throws(XmlPullParserException::class, IOException::class)
    private fun parseKeyLayout(
        context: Context,
        xmlParser: XmlResourceParser
    ): LinearLayout {
        val linearLayout = LinearLayout(context)
        linearLayout.layoutParams = LayoutParams(
            xmlParser.getAttributeIntValue(
                null,
                "width",
                LayoutParams.MATCH_PARENT
            ),
            xmlParser.getAttributeIntValue(null, "height", 0),
            xmlParser.getAttributeFloatValue(null, "weight", 1.0f)
        )
        linearLayout.orientation = xmlParser.getAttributeIntValue(
            null, "orientation",
            HORIZONTAL
        )
        var tag: String
        do {
            xmlParser.next()
            tag = xmlParser.name
            if (xmlParser.eventType == XmlResourceParser.START_TAG) {
                if (tag == XML_TAG_LAYOUT) {
                    linearLayout.addView(parseKeyLayout(context, xmlParser))
                } else if (tag == XML_TAG_KEY) {
                    val attrs = Key.KeyAttributes()
                    attrs.keyFunction = getStringAttributeValue(xmlParser, "keyFunc", "")
                    attrs.mainLabel = getStringAttributeValue(xmlParser, "keyLabel", "")
                    attrs.shiftLabel = getStringAttributeValue(xmlParser, "shiftLabel", "")
                    attrs.keyCode = xmlParser.getAttributeIntValue(null, "keyCode", 0)
                    val key = Key(context, attrs)
                    key.layoutParams = LayoutParams(
                        xmlParser.getAttributeIntValue(null, "width", 0),
                        xmlParser.getAttributeIntValue(
                            null, "height",
                            LayoutParams.MATCH_PARENT
                        ),
                        xmlParser.getAttributeFloatValue(null, "weight", 1f)
                    )
                    key.visibility = if (xmlParser.getAttributeBooleanValue(
                            null,
                            "visible",
                            true
                        )
                    ) View.VISIBLE else View.INVISIBLE
                    key.setKeyListener(this)
                    if (attrs.shiftLabel.isNotEmpty() && attrs.shiftLabel.isNotEmpty()) {
                        mKeysWithShiftLabel.add(key)
                    }
                    linearLayout.addView(key)
                }
            }
        } while (xmlParser.eventType != XmlResourceParser.END_TAG
            || tag != XML_TAG_LAYOUT
        )
        return linearLayout
    }

    private fun getStringAttributeValue(
        xmlParser: XmlResourceParser, attributeName: String,
        defaultValue: String
    ): String {
        val value = xmlParser.getAttributeValue(null, attributeName)
        return value ?: defaultValue
    }

    private fun updateShiftlabel(shiftOn: Boolean) {
        for (key in mKeysWithShiftLabel) {
            key.setShiftState(shiftOn)
        }
    }

    private fun dispatchOnKeyDown(keyCode: Int) {
        if (mKeyboardListener != null) {
            mKeyboardListener!!.onKeyDown(keyCode)
        }
    }

    private fun dispatchOnKeyUp(keyCode: Int) {
        if (mKeyboardListener != null) {
            mKeyboardListener!!.onKeyUp(keyCode)
        }
    }

    fun setKeyboardListener(keyboardListener: KeyboardListener?) {
        mKeyboardListener = keyboardListener
    }

    override fun onKeyDown(keyFunction: String, keyCode: Int) {
        dispatchOnKeyDown(keyCode)
        if (keyFunction.toLowerCase().contains("shift")) {
            if (mShiftCount == 0) {
                updateShiftlabel(true)
            }
            mShiftCount++
        }
    }

    override fun onKeyUp(keyFunction: String, keyCode: Int) {
        dispatchOnKeyUp(keyCode)
        if (mShiftCount > 0 && keyFunction.toLowerCase().contains("shift")) {
            mShiftCount--
            if (mShiftCount == 0) {
                updateShiftlabel(false)
            }
        }
    }

    companion object {
        private const val XML_TAG_LAYOUT = "Layout"
        private const val XML_TAG_KEY = "Key"
    }

}